// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CommandLine;
using Microsoft.DotNet.Cli.Commands.Tool.Common;
using Microsoft.DotNet.Cli.Commands.Tool.List;
using Microsoft.DotNet.Cli.Commands.Tool.Update;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.ToolManifest;
using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.Extensions.EnvironmentAbstractions;

namespace Microsoft.DotNet.Cli.Commands.Tool.Install;

internal class ToolInstallLocalCommand : CommandBase
{
    private readonly IToolManifestFinder _toolManifestFinder;
    private readonly IToolManifestEditor _toolManifestEditor;
    private readonly ILocalToolsResolverCache _localToolsResolverCache;
    private readonly ToolInstallLocalInstaller _toolLocalPackageInstaller;
    private readonly IReporter _reporter;
    private readonly PackageId? _packageId;
    private readonly bool _allowPackageDowngrade;

    private readonly string _explicitManifestFile;
    private readonly bool _createManifestIfNeeded;
    private readonly bool _allowRollForward;
    private readonly bool _updateAll;

    internal RestoreActionConfig restoreActionConfig;

    public ToolInstallLocalCommand(
        ParseResult parseResult,
        PackageId? packageId = null,
        IToolPackageDownloader toolPackageDownloader = null,
        IToolManifestFinder toolManifestFinder = null,
        IToolManifestEditor toolManifestEditor = null,
        ILocalToolsResolverCache localToolsResolverCache = null,
        IReporter reporter = null,
        string runtimeJsonPathForTests = null
        )
        : base(parseResult)
    {
        _updateAll = parseResult.GetValue(ToolUpdateCommandParser.UpdateAllOption);
        var packageIdArgument = parseResult.GetValue(ToolInstallCommandParser.PackageIdentityArgument)?.Id;
        _packageId = packageId ?? (packageIdArgument is not null ? new PackageId(packageIdArgument) : null);
        _explicitManifestFile = parseResult.GetValue(ToolAppliedOption.ToolManifestOption);

        _createManifestIfNeeded = parseResult.GetValue(ToolInstallCommandParser.CreateManifestIfNeededOption);

        _reporter = reporter ?? Reporter.Output;

        _toolManifestFinder = toolManifestFinder ??
                              new ToolManifestFinder(new DirectoryPath(Directory.GetCurrentDirectory()));
        _toolManifestEditor = toolManifestEditor ?? new ToolManifestEditor();
        _localToolsResolverCache = localToolsResolverCache ?? new LocalToolsResolverCache();

        restoreActionConfig = new RestoreActionConfig(DisableParallel: parseResult.GetValue(ToolCommandRestorePassThroughOptions.DisableParallelOption),
            NoCache: parseResult.GetValue(ToolCommandRestorePassThroughOptions.NoCacheOption) || parseResult.GetValue(ToolCommandRestorePassThroughOptions.NoHttpCacheOption),
            IgnoreFailedSources: parseResult.GetValue(ToolCommandRestorePassThroughOptions.IgnoreFailedSourcesOption),
            Interactive: parseResult.GetValue(ToolCommandRestorePassThroughOptions.InteractiveRestoreOption));

        _toolLocalPackageInstaller = new ToolInstallLocalInstaller(parseResult, toolPackageDownloader, runtimeJsonPathForTests, restoreActionConfig);
        _allowRollForward = parseResult.GetValue(ToolInstallCommandParser.RollForwardOption);
        _allowPackageDowngrade = parseResult.GetValue(ToolInstallCommandParser.AllowPackageDowngradeOption);
    }

    public override int Execute()
    {
        if (_updateAll)
        {
            var toolListCommand = new ToolListLocalCommand(_parseResult, (IToolManifestInspector)_toolManifestFinder);
            var toolIds = toolListCommand.GetPackages(null);
            foreach (var toolId in toolIds)
            {
                ExecuteInstallCommand(toolId.Item1.PackageId);
            }
            return 0;
        }
        else
        {
            return ExecuteInstallCommand((PackageId) _packageId);
        }
    }

    private int ExecuteInstallCommand(PackageId packageId)
    {
        FilePath manifestFile = GetManifestFilePath();

        (FilePath? manifestFileOptional, string warningMessage) =
            _toolManifestFinder.ExplicitManifestOrFindManifestContainPackageId(_explicitManifestFile, packageId);

        if (warningMessage != null)
        {
            _reporter.WriteLine(warningMessage.Yellow());
        }

        manifestFile = manifestFileOptional ?? GetManifestFilePath();
        var existingPackageWithPackageId = _toolManifestFinder.Find(manifestFile).Where(p => p.PackageId.Equals(packageId));

        if (!existingPackageWithPackageId.Any())
        {
            return InstallNewTool(manifestFile, packageId);
        }

        var existingPackage = existingPackageWithPackageId.Single();
        var toolDownloadedPackage = _toolLocalPackageInstaller.Install(manifestFile, packageId);

        InstallToolUpdate(existingPackage, toolDownloadedPackage, manifestFile, packageId);

        _localToolsResolverCache.SaveToolPackage(
            toolDownloadedPackage,
            _toolLocalPackageInstaller.TargetFrameworkToInstall);

        return 0;
    }

    public int InstallToolUpdate(ToolManifestPackage existingPackage, IToolPackage toolDownloadedPackage, FilePath manifestFile, PackageId packageId)
    {
        if (existingPackage.Version > toolDownloadedPackage.Version && !_allowPackageDowngrade)
        {
            throw new GracefulException(
                [
                    string.Format(
                        CliCommandStrings.UpdateLocalToolToLowerVersion,
                        toolDownloadedPackage.Version.ToNormalizedString(),
                        existingPackage.Version.ToNormalizedString(),
                        manifestFile.Value)
                ],
                isUserError: false);
        }
        else if (existingPackage.Version == toolDownloadedPackage.Version)
        {
            _reporter.WriteLine(
                string.Format(
                    CliCommandStrings.UpdateLocaToolSucceededVersionNoChange,
                    toolDownloadedPackage.Id,
                    existingPackage.Version.ToNormalizedString(),
                    manifestFile.Value));
        }
        else
        {
            _toolManifestEditor.Edit(
                manifestFile,
                packageId,
                toolDownloadedPackage.Version,
                [toolDownloadedPackage.Command.Name]);
            _reporter.WriteLine(
                string.Format(
                    CliCommandStrings.UpdateLocalToolSucceeded,
                    toolDownloadedPackage.Id,
                    existingPackage.Version.ToNormalizedString(),
                    toolDownloadedPackage.Version.ToNormalizedString(),
                    manifestFile.Value).Green());
        }

        _localToolsResolverCache.SaveToolPackage(
            toolDownloadedPackage,
            _toolLocalPackageInstaller.TargetFrameworkToInstall);

        return 0;
    }

    public int InstallNewTool(FilePath manifestFile, PackageId packageId)
    {
        IToolPackage toolDownloadedPackage =
            _toolLocalPackageInstaller.Install(manifestFile, packageId);

        _toolManifestEditor.Add(
            manifestFile,
            toolDownloadedPackage.Id,
            toolDownloadedPackage.Version,
            [toolDownloadedPackage.Command.Name],
            _allowRollForward);

        _localToolsResolverCache.SaveToolPackage(
            toolDownloadedPackage,
            _toolLocalPackageInstaller.TargetFrameworkToInstall);

        _reporter.WriteLine(
            string.Format(
                CliCommandStrings.LocalToolInstallationSucceeded,
                toolDownloadedPackage.Command.Name,
                toolDownloadedPackage.Id,
                toolDownloadedPackage.Version.ToNormalizedString(),
                manifestFile.Value).Green());

        return 0;
    }

    public FilePath GetManifestFilePath()
    {
        try
        {
            return string.IsNullOrWhiteSpace(_explicitManifestFile)
                ? _toolManifestFinder.FindFirst(_createManifestIfNeeded)
                : new FilePath(_explicitManifestFile);
        }
        catch (ToolManifestCannotBeFoundException e)
        {
            throw new GracefulException(
                [e.Message, CliCommandStrings.ToolInstallNoManifestGuide],
                verboseMessages: [e.VerboseMessage],
                isUserError: false);
        }
    }
}
